using System;
using Firefly.ExtensionMethods;
using Firefly.Framework.Components.Cameras;
using Firefly.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Firefly.Framework.Components.Sky
{
    public class SkyDome : DrawableComponent, ISkyDome
    {
        private Texture2D _mieTexture;
        private Texture2D _rayleighTexture;
        private Moon _moon;
        private float _alpha;
        private RenderTarget2D _mieRenderTarget;
        private RenderTarget2D _rayleighRenderTarget;
        private DepthStencilBuffer _oldDepthBuffer;
        private DepthStencilBuffer _newDepthBuffer;

        public Dome Dome { get; set; }
        public PerlinClouds PerlinClouds { get; private set; }

        public SkyDome(Game game, ICamera camera)
            : this(game, 32, 12, false, 3.5f, 0.5f, camera)
        {
        }

        public SkyDome(Game game, int domeSize, int numberOfTiles, bool realTime, float theta, float phi, ICamera camera)
            : base(game)
        {
            Dome = new Dome(domeSize);
            RealTime = realTime;
            Theta = theta;
            Phi = phi;
            Camera = camera;
        }

        private void UpdateMieRayleighTextures(GameTime gameTime)
        {
            GraphicsService.Instance.DepthStencilBuffer = _newDepthBuffer;
            GraphicsService.Instance.SetRenderTarget(0, _rayleighRenderTarget);
            GraphicsService.Instance.SetRenderTarget(1, _mieRenderTarget);
            Shader.SetTechnique(SkyDomeShader.Technique.Update);
            Shader.Draw(gameTime);
            GraphicsService.Instance.SetRenderTarget(1, null);
            GraphicsService.Instance.SetRenderTarget(0, null);
            _mieTexture = _mieRenderTarget.GetTexture();
            _rayleighTexture = _rayleighRenderTarget.GetTexture();
            GraphicsService.Instance.DepthStencilBuffer = _oldDepthBuffer;
        }

        private Vector3 GetSunlightDirection()
        {
            float y = (float)Math.Cos((double)Theta);
            float x = (float)(Math.Sin((double)Theta) * Math.Cos(Phi));
            float z = (float)(Math.Sin((double)Theta) * Math.Sin(Phi));
            return new Vector3(x, y, z);
        }

        private Vector4 GetSunlightColor(float theta, int turbidity)
        {
            float beta = (0.04608365822050f * turbidity) - 0.04586025928522f;
            float tuR;
            float tauA;
            float[] tau = new float[3];
            float cosine = (float)Math.Cos((double)theta + Math.PI);
            double factor = (double)theta / Math.PI * 180.0;
            double jarl = Math.Pow(93.885 - factor, -1.253);
            float potential = (float)jarl;
            float m = 1.0f / (cosine + (0.15f * potential));
            float Alpha = 1.3f;
            float[] lambda = new float[3];
            lambda[0] = Shader.Wavelength.X;
            lambda[1] = Shader.Wavelength.Y;
            lambda[2] = Shader.Wavelength.Z;

            for (int i = 0; i < 3; i++)
            {
                potential = (float)Math.Pow((double)lambda[i], 4.0);
                tuR = (float)Math.Exp((double)(-m * 0.008735f * potential));
                potential = (float)Math.Pow((double)lambda[i], (double)-Alpha);

                if (m < 0.0f)
                {
                    tau[i] = 0.0f;
                }
                else
                {
                    tauA = (float)Math.Exp((double)(-m * beta * potential));
                    tau[i] = tuR * tauA;
                }
            }

            return new Vector4(tau[0], tau[1], tau[2], 1.0f);
        }

        #region ISkyDome Members

        public SkyDomeShader Shader { get; protected set; }
        public bool RealTime { get; set; }
        public Vector3 SunlightDirection { get; protected set; }
        public Vector4 SunlightColor { get; protected set; }
        public float Theta { get; set; }
        public float Phi { get; set; }

        #endregion

        #region IHasCamera Members

        public ICamera Camera { get; set; }

        #endregion

        #region IUpdateable Members

        public override void Update(GameTime gameTime)
        {
            if (Enabled)
            {
                if (RealTime)
                {
                    int minutes = (DateTime.Now.Hour * 60) + DateTime.Now.Minute;
                    Theta = (float)minutes * (float)Math.PI / 12.0f / 60.0f;
                }

                if (Theta < Math.PI / 2.0f || Theta > 3.0f * Math.PI / 2.0f)
                {
                    _alpha = (float)Math.Abs(Math.Sin(Theta + ((float)Math.PI / 2.0f)));
                }
                else
                {
                    _alpha = 0.0f;
                }

                SunlightDirection = GetSunlightDirection();
                SunlightDirection.Normalize();
                SunlightDirection = SunlightDirection.Inverse();
                SunlightColor = GetSunlightColor(-Theta, 2);
                _moon.Theta = Theta;
                _moon.Phi = Phi;
                _moon.SunlightDirection = SunlightDirection.Inverse();
                _moon.Alpha = _alpha;
                PerlinClouds.SunlightColor = SunlightColor;
            }
        }

        #endregion

        #region IDrawable Members

        public override void Draw(GameTime gameTime)
        {
            if (Visible)
            {
                if (GraphicsService.Instance.RenderState.FillMode == FillMode.Solid)
                {
                    UpdateMieRayleighTextures(gameTime);
                    GraphicsService.Instance.RenderState.DepthBufferEnable = false;
                    GraphicsService.Instance.RenderState.DepthBufferWriteEnable = false;
                    GraphicsService.Instance.RenderState.CullMode = CullMode.None;
                    Shader.SetTechnique(SkyDomeShader.Technique.Render);
                    Shader.View = Camera.View;
                    Shader.Projection = Camera.Projection;
                    Shader.World = Matrix.CreateTranslation(Camera.Position.X, Camera.Position.Y, Camera.Position.Z);
                    Shader.SunlightDirection = SunlightDirection;
                    Shader.StarIntensity = _alpha;
                    Shader.MieTexture = _mieTexture;
                    Shader.RayleighTexture = _rayleighTexture;
                    Shader.Draw(gameTime);
                    base.Draw(gameTime);
                    GraphicsService.Instance.RenderState.DepthBufferWriteEnable = true;
                    GraphicsService.Instance.RenderState.DepthBufferEnable = true;
                }
            }
        }

        #endregion

        #region IHasContent Members

        public override void LoadContent()
        {
#if XBOX
            _mieRenderTarget = new RenderTarget2D(GraphicsService.Instance.GraphicsDevice, 256, 128, 1, SurfaceFormat.Color, MultiSampleType.None, 0);
            _rayleighRenderTarget = new RenderTarget2D(GraphicsService.Instance.GraphicsDevice, 256, 128, 1, SurfaceFormat.Color, MultiSampleType.None, 0);
#else
            _mieRenderTarget = new RenderTarget2D(GraphicsService.Instance.GraphicsDevice, 128, 64, 1, SurfaceFormat.HalfVector4, MultiSampleType.None, 0);
            _rayleighRenderTarget = new RenderTarget2D(GraphicsService.Instance.GraphicsDevice, 128, 64, 1, SurfaceFormat.HalfVector4, MultiSampleType.None, 0);
#endif

            _oldDepthBuffer = GraphicsService.Instance.DepthStencilBuffer;
            _newDepthBuffer = new DepthStencilBuffer(
                GraphicsService.Instance.GraphicsDevice,
                GraphicsService.Instance.PresentationParameters.BackBufferWidth,
                GraphicsService.Instance.PresentationParameters.BackBufferHeight,
                GraphicsService.Instance.DepthStencilBuffer.Format,
                MultiSampleType.None,
                0);
            _moon = new Moon(Game, "Textures\\Skies\\moon", "Textures\\Skies\\moonGlow", Camera);
            _moon.UpdateOrder = 1;
            _moon.DrawOrder = 1;
            Children.Add(_moon);
            PerlinClouds = new PerlinClouds(Game, Camera);
            PerlinClouds.UpdateOrder = 2;
            PerlinClouds.DrawOrder = 2;
            Children.Add(PerlinClouds);
            Shader = new SkyDomeShader(this, "Textures\\Skies\\starfield");
            Shader.LoadContent();
            Shader.NumberOfSamples = 20;
            base.LoadContent();
        }

        public override void UnloadContent()
        {
            if (!Disposed)
            {
                if (_mieRenderTarget != null)
                {
                    _mieRenderTarget.Dispose();
                    _mieRenderTarget = null;
                }

                if (_rayleighRenderTarget != null)
                {
                    _rayleighRenderTarget.Dispose();
                    _rayleighRenderTarget = null;
                }

                Shader.Dispose();
                _moon.Dispose();
                PerlinClouds.Dispose();
            }
        }

        #endregion

        #region IDisposable Members

        ~SkyDome()
        {
            Dispose(false);
        }

        #endregion
    }
}